package handler;

import jiyici.Client2ServerSyncData;
import jiyici.Server2ClientSyncData;
import jiyici.Server2ClientSyncItem;
import jiyici.Constant.AuthenticateKey;
import jiyici.Constant.ErrorCode;
import jiyici.Constant.ItemParamType;
import jiyici.Constant.ModelColumn;
import jiyici.Constant.PostAction;
import models.Category;
import models.Item;
import models.ItemParam;
import models.Record;
import models.RecordParam;
import models.User;

import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.node.ArrayNode;

import play.mvc.Http.Request;
import play.mvc.Result;
import util.ModelUtil;

import com.avaje.ebean.validation.factory.EmailValidation;

import data.ServerSyncManager;

public class PostHandler {

    public static Result handleAction(final int action, final Request request) {
        final JsonNode requestJson = request.body().asJson();
        if (requestJson == null) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_NO_REQUEST_DATA);
        }
        switch (action) {
        case PostAction.REGISTER:
            return handleActionForRegister(action, requestJson);
        case PostAction.LOGIN:
            return handleActionForLogin(action, requestJson);
        default:
            return handleAuthenticatedAction(action, requestJson);
        }
    }

    private static Result handleAuthenticatedAction(final int action, final JsonNode requestJson) {
        final User user = getUserFromJsonData(requestJson);
        if (user == null) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_NOT_AUTHENTICATED);
        }
        final JsonNode dataNode = requestJson.findPath("data");
        if (dataNode == null || !dataNode.isObject()) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_INVALID_DATA_FORMAT);
        }
        switch (action) {
        case PostAction.SYNC_SERVER_TO_CLIENT:
            return handleActionForSyncServer2Client(action, user, dataNode);
        case PostAction.SYNC_CLIENT_TO_SERVER:
            return handleActionForSyncClient2Server(action, user, dataNode);
        case PostAction.ADD_ITEM:
            return handleActionForAddItem(action, user, dataNode);
        case PostAction.RECORD:
            return handleActionForRecord(action, dataNode);
        case PostAction.ADD_CATEGORY:
            return handleActionForAddCategory(action, user, dataNode);
        case PostAction.REMOVE_CATEGORY:
            return handleActionForRemoveCategory(action, dataNode);
        default:
            return HandlerUtil.responseError(action, ErrorCode.ERROR_ACTION_NOT_SUPPORTED);
        }
    }

    private static Result handleActionForRegister(final int action, final JsonNode requestJson) {
        String email = requestJson.findPath(ModelColumn.USER_COLUMN_EMAIL).getTextValue();
        String userName = requestJson.findPath(ModelColumn.USER_COLUMN_USERNAME).getTextValue();
        String password = requestJson.findPath(ModelColumn.USER_COLUMN_PASSWORD).getTextValue();

        if (StringUtils.isBlank(email) || StringUtils.isBlank(userName) || StringUtils.isBlank(password)
                || !EmailValidation.isValidEmail(email)) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_INVALID_DATA_FORMAT);
        }
        email = StringUtils.trim(email);
        userName = StringUtils.trim(userName);
        password = StringUtils.trim(password);

        User user = User.register(email, userName, password);
        if (user == null) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_EMAIL_EXIST);
        }
        return HandlerUtil.responseSuccess(action, ModelUtil.jsonOf(user));
    }

    private static Result handleActionForLogin(final int action, final JsonNode requestJson) {
        String email = requestJson.findPath(ModelColumn.USER_COLUMN_EMAIL).getTextValue();
        String password = requestJson.findPath(ModelColumn.USER_COLUMN_PASSWORD).getTextValue();

        if (StringUtils.isBlank(email) || StringUtils.isBlank(password)) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_INVALID_DATA_FORMAT);
        }
        email = StringUtils.trim(email);
        password = StringUtils.trim(password);

        User user = User.authenticateByEmailPassword(email, password);
        if (user == null) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_LOGIN_FAILED);
        }
        return HandlerUtil.responseSuccess(action, ModelUtil.jsonOf(user));
    }

    private static User getUserFromJsonData(final JsonNode requestJson) {
        long userId = requestJson.findPath(AuthenticateKey.USER_ID).getLongValue();
        String token = requestJson.findPath(AuthenticateKey.TOKEN).getTextValue();
        if (userId <= 0 || StringUtils.isBlank(token)) {
            return null;
        }
        return User.authenticateByIdToken(userId, token);
    }
    
    private static Result handleActionForSyncServer2Client(final int action, final User user, final JsonNode dataNode) {
        JsonNode arrayNode = dataNode.findPath("syncData");
        Server2ClientSyncData syncData = new Server2ClientSyncData();
        try {
            syncData.fromJson(arrayNode);
        } catch (DataInvalidException e) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_INVALID_DATA_FORMAT);
        }
        return HandlerUtil.responseSuccess(action, ServerSyncManager.syncServer2Client(user, syncData));
    }
    
    private static Result handleActionForSyncClient2Server(final int action, final User user, final JsonNode dataNode) {
        JsonNode arrayNode = dataNode.findPath("dirty");
        Client2ServerSyncData syncData = new Client2ServerSyncData();
        try {
            syncData.fromJson(arrayNode);
        } catch (DataInvalidException e) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_INVALID_DATA_FORMAT);
        }
        return HandlerUtil.responseSuccess(action, ServerSyncManager.syncClient2Server(syncData));
    }

    private static Result handleActionForAddItem(final int action, final User user, final JsonNode dataNode) {
        String name = dataNode.findPath(ModelColumn.ITEM_COLUMN_NAME).getTextValue();
        long category = dataNode.findPath(ModelColumn.ITEM_COLUMN_CATEGORYID).getLongValue();
        JsonNode paramsNode = dataNode.findPath(ModelColumn.ITEM_COLUMN_PARAMS);

        if (StringUtils.isBlank(name) || paramsNode == null || !paramsNode.isArray()) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_INVALID_DATA_FORMAT);
        }

        ItemParam[] params = parseItemParams(paramsNode);
        if (params == null) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_INVALID_DATA_FORMAT);
        }

        name = StringUtils.trim(name);

        Item item = Item.addItem(user, name, category, params);
        if (item == null) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_SYSTEM_ERROR);
        }
        return HandlerUtil.responseSuccess(action, ModelUtil.jsonOf(item));
    }

    private static ItemParam[] parseItemParams(JsonNode paramsNode) {
        int size = paramsNode.size();
        ItemParam[] params = new ItemParam[size];
        for (int i = 0; i < size; i++) {
            JsonNode paramNode = paramsNode.get(i);
            String name = paramNode.findPath(ModelColumn.ITEM_PARAM_COLUMN_NAME).getTextValue();
            int type = paramNode.findPath(ModelColumn.ITEM_PARAM_COLUMN_TYPE).getIntValue();
            boolean isMust = paramNode.findPath(ModelColumn.ITEM_PARAM_COLUMN_IS_MUST).getBooleanValue();
            if (!StringUtils.isBlank(name) && (type >= ItemParamType.FIRST && type <= ItemParamType.LAST)) {
                name = StringUtils.trim(name);
                ItemParam param = new ItemParam();
                param.itemParamName = name;
                param.paramType = type;
                param.isMust = isMust;
                params[i] = param;
            } else {
                return null;
            }
        }
        return params;
    }

    private static Result handleActionForRecord(final int action, final JsonNode dataNode) {
        long itemId = dataNode.findPath(ModelColumn.RECORD_ITEM_ID).getLongValue();
        long recordTime = dataNode.findPath(ModelColumn.RECORD_RECORD_TIME).getLongValue();
        JsonNode paramsNode = dataNode.findPath(ModelColumn.RECORD_PARAMS);

        if (itemId <= 0 || recordTime <= 0 || paramsNode == null || !paramsNode.isArray()) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_INVALID_DATA_FORMAT);
        }

        RecordParam[] params = parseRecordParams(paramsNode);
        if (params == null) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_INVALID_DATA_FORMAT);
        }

        Record record = Record.addRecord(itemId, recordTime, params);
        if (record == null) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_SYSTEM_ERROR);
        }
        return HandlerUtil.responseSuccess(action, ModelUtil.jsonOf(record));
    }

    private static RecordParam[] parseRecordParams(JsonNode paramsNode) {
        int size = paramsNode.size();
        RecordParam[] params = new RecordParam[size];
        for (int i = 0; i < size; i++) {
            JsonNode paramNode = paramsNode.get(i);
            long itemParamId = paramNode.findPath(ModelColumn.RECORD_PARAM_ITEM_PARAM_ID).getLongValue();
            String value = paramNode.findPath(ModelColumn.RECORD_PARAM_VALUE).getTextValue();
            if (itemParamId > 0
                    && !StringUtils.isBlank(value)) {
                value = StringUtils.trim(value);
                RecordParam param = new RecordParam();
                param.itemParamId = itemParamId;
                param.value = value;
                params[i] = param;
            } else {
                return null;
            }
        }
        return params;
    }

    private static Result handleActionForChangePassword(final int action, final User user, final JsonNode dataNode) {
        String oldPassword = dataNode.findPath("old_password").getTextValue();
        String newPassword = dataNode.findPath("new_password").getTextValue();
        if (StringUtils.isBlank(oldPassword) || StringUtils.isBlank(newPassword)) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_INVALID_DATA_FORMAT);
        }
        /* TODO not finished */
        User.changePassword(user, oldPassword, newPassword);
        return null;
    }
    
    private static Result handleActionForAddCategory(final int action, final User user, final JsonNode dataNode) {
        String name = dataNode.findPath(ModelColumn.CATEGORY_COLUMN_NAME).getTextValue();
        String displayOrder = dataNode.findPath(ModelColumn.CATEGORY_COLUMN_DISPLAY_ORDER).getTextValue();
        if (StringUtils.isBlank(name) || StringUtils.isBlank(displayOrder)) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_INVALID_DATA_FORMAT);
        }
        
        Category category = Category.addCategory(user, name, displayOrder);
        return HandlerUtil.responseSuccess(action, ModelUtil.jsonOf(category));
    }

    private static Result handleActionForRemoveCategory(final int action, final JsonNode dataNode) {
        long id = dataNode.findPath(ModelColumn.BASE_COLUMN_ID).getLongValue();
        if (id <= 0) {
            return HandlerUtil.responseError(action, ErrorCode.ERROR_INVALID_DATA_FORMAT);
        }
        Category.removeCategory(id);
        return HandlerUtil.responseSuccess(action);
    }
}
